package com.ccrfid.rmc6.service.poi;

import com.ccrfid.rmc6.appconfig.exception.pojo.AppException;
import com.ccrfid.rmc6.appconfig.exception.pojo.CustomErrors;
import com.ccrfid.rmc6.entity.poi.PoiLocationEntity;
import com.ccrfid.rmc6.entity.sys.DictEntity;
import com.ccrfid.rmc6.pojo.dto.LocationBindPersonnelDto;
import com.ccrfid.rmc6.pojo.dto.LocationPersonnelDto;
import com.ccrfid.rmc6.repository.poi.PoiLocationRepository;
import com.ccrfid.rmc6.repository.search.SearchCriteria;
import com.ccrfid.rmc6.repository.sys.OrgRepository;
import com.ccrfid.rmc6.service.sys.DictService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import javax.ws.rs.core.Response;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
public class PoiLocationServiceImpl implements PoiLocationService {
    private static final Logger logger = LoggerFactory.getLogger(PoiLocationServiceImpl.class);
    @Autowired
    private PoiLocationRepository poiLocationRepository;
    @Autowired
    private OrgRepository orgRepository;
    @Autowired
    private EntityManager entityManager;
    @Autowired
    private DictService dictService;

    @Override
    public List<PoiLocationEntity> getPoiLocationList(SearchCriteria criteria, Sort sort) {
        return poiLocationRepository.search(criteria, sort);
    }

    @Override
    public Page<PoiLocationEntity> getPoiLocationPageList(SearchCriteria criteria, Pageable pageable) {
        return poiLocationRepository.search(criteria, pageable);
    }

    @Override
    public List<PoiLocationEntity> getAllLocations() {
        return poiLocationRepository.getAllBy();
    }

    @Override
    public PoiLocationEntity getPoiLocationById(Long locationId) {
        return poiLocationRepository.getById(locationId);
    }

    @Override
    public PoiLocationEntity createPoiLocation(PoiLocationEntity entity) throws AppException {
        if (!StringUtils.isEmpty(entity.getLocationSn())) {
            if (poiLocationRepository.existsByLocationSn(entity.getLocationSn())) {
                throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                        CustomErrors.sn_conflict.getCode(), CustomErrors.sn_conflict.getReason(), "");
            }
        }
        return poiLocationRepository.save(entity);
    }

    @Override
    public PoiLocationEntity updatePoiLocation(Long locationId, PoiLocationEntity entity) throws AppException {
        if (!poiLocationRepository.existsById(locationId)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
        if (!locationId.equals(entity.getId())) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.different_args.getCode(), CustomErrors.different_args.getReason(), "");
        }
        if (!StringUtils.isEmpty(entity.getLocationSn())) {
            if (poiLocationRepository.existsByLocationSnAndIdNot(entity.getLocationSn(), locationId)) {
                throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                        CustomErrors.sn_conflict.getCode(), CustomErrors.sn_conflict.getReason(), "");
            }
        }
        return poiLocationRepository.save(entity);
    }

    @Override
    public int deletePoiLocations(List<Long> idList) {
        int count = 0;
        for (Long id : idList) {
            if (poiLocationRepository.existsById(id)) {
                count++;
            }
        }
        if (count > 0) {
            poiLocationRepository.deleteByIdIn(idList);
        }
        return count;
    }

    @Override
    public PoiLocationEntity bindLocationOrg(Long locationId, Long orgId) throws AppException {
        if (!poiLocationRepository.existsById(locationId)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
        if (!orgRepository.existsById(orgId)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.org_not_found.getCode(), CustomErrors.org_not_found.getReason(), "");
        }
        PoiLocationEntity locationEntity = poiLocationRepository.getById(locationId);
        locationEntity.setBindOrgId(orgId);
        return poiLocationRepository.save(locationEntity);
    }

    @Override
    public PoiLocationEntity unBindLocationOrg(Long locationId) throws AppException {
        if (!poiLocationRepository.existsById(locationId)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
        PoiLocationEntity locationEntity = poiLocationRepository.getById(locationId);
        locationEntity.setBindOrgId(null);
        return poiLocationRepository.save(locationEntity);
    }

    @Override
    public Page<LocationPersonnelDto> getPersonnelsInfoInLocation(Long locationId, Pageable pageable) {
        PoiLocationEntity locationEntity = poiLocationRepository.getById(locationId);

        String sql = "select p.id as personnel_id, p.personnel_name, p.card_number, p.personnel_sn, p.personnel_type, p.personnel_type_name, p.org_id, p.org_name, p.locator_address, " +
                " t.id as location_id, t.location_name, t.location_type, t.map_level, t.floor_number, t.bind_org_id, " +
                " pp.is_death_prisoner, pp.is_focused_prisoner, pp.is_infected_prisoner, pp.is_major_criminal_offender " +
                " from v_personnel p " +
                " left join (select * from poi_location where map_level = :maplevel and floor_number = :fnumber) t on st_contains(t.location_polygon, p.pos) " +
                " left join personnel_prisoner pp on p.id = pp.personnel_id " +
                " where p.is_enabled = 1 and p.org_id = :bindOrg " +
                " union " +
                "select p.id as personnel_id, p.personnel_name, p.card_number, p.personnel_sn, p.personnel_type, p.personnel_type_name, p.org_id, p.org_name, p.locator_address, " +
                " t.id as location_id, t.location_name, t.location_type, t.map_level, t.floor_number, t.bind_org_id, " +
                " pp.is_death_prisoner, pp.is_focused_prisoner, pp.is_infected_prisoner, pp.is_major_criminal_offender " +
                " from v_personnel p " +
                " left join (select * from poi_location where map_level = :maplevel and floor_number = :fnumber) t on st_contains(t.location_polygon, p.pos) " +
                " left join personnel_prisoner pp on p.id = pp.personnel_id " +
                " where p.is_enabled = 1 and p.floor_number = :fnumber and st_contains(:pg, p.pos)";

        String countSql = "select count(*) as total from (" + sql + ") as t";
        BigInteger total = (BigInteger) entityManager.createNativeQuery(countSql)
                .setParameter("maplevel", locationEntity.getMapLevel())
                .setParameter("fnumber", locationEntity.getFloorNumber())
                .setParameter("bindOrg", locationEntity.getBindOrgId())
                .setParameter("pg", locationEntity.getLocationPolygon())
                .getSingleResult();
        List<LocationPersonnelDto> content = entityManager.createNativeQuery(sql, LocationPersonnelDto.class)
                .setParameter("maplevel", locationEntity.getMapLevel())
                .setParameter("fnumber", locationEntity.getFloorNumber())
                .setParameter("bindOrg", locationEntity.getBindOrgId())
                .setParameter("pg", locationEntity.getLocationPolygon())
                .setFirstResult((int) pageable.getOffset())
                .setMaxResults(pageable.getPageSize())
                .getResultList();

        return new PageImpl<LocationPersonnelDto>(content, pageable, total.longValue());
    }

    @Override
    public Page<LocationPersonnelDto> getActualPersonnelsInfoInLocation(Long locationId, Integer personnelType, Pageable pageable) {
        PoiLocationEntity locationEntity = poiLocationRepository.getById(locationId);

        String sql = "select p.id as personnel_id, p.personnel_name, p.card_number, p.personnel_sn, p.personnel_type, p.personnel_type_name, p.org_id, p.org_name, p.locator_address, " +
                " t.id as location_id, t.location_name, t.location_type, t.map_level, t.floor_number, t.bind_org_id, " +
                " pp.is_death_prisoner, pp.is_focused_prisoner, pp.is_infected_prisoner, pp.is_major_criminal_offender, p.online_status " +
                " from v_personnel p " +
                " left join (select * from poi_location where map_level = 3 and floor_number = :fnumber) t on st_contains(t.location_polygon, p.pos) " +
                " left join personnel_prisoner pp on p.id = pp.personnel_id " +
                " where p.is_enabled = 1 and p.floor_number = :fnumber and st_contains(:pg, p.pos)";
        List<LocationPersonnelDto> content = new ArrayList<>();
        BigInteger total = null;
        if(personnelType!=null){
            List<DictEntity> personnlDictList = dictService.getDictAndSubDictListById(personnelType);
            List<Integer> personnelTypeList = new ArrayList<>();
            for (DictEntity dictEntity : personnlDictList) {
                personnelTypeList.add(dictEntity.getId());
            }
            sql = sql + " and p.personnel_type in :personnelTypeList";
            content = entityManager.createNativeQuery(sql, LocationPersonnelDto.class)
                    .setParameter("fnumber", locationEntity.getFloorNumber())
                    .setParameter("pg", locationEntity.getLocationPolygon())
                    .setParameter("personnelTypeList",personnelTypeList)
                    .setFirstResult((int) pageable.getOffset())
                    .setMaxResults(pageable.getPageSize())
                    .getResultList();
            String countSql = "select count(*) as total from (" + sql + ") as t";
            total = (BigInteger) entityManager.createNativeQuery(countSql)
                    .setParameter("fnumber", locationEntity.getFloorNumber())
                    .setParameter("pg", locationEntity.getLocationPolygon())
                    .setParameter("personnelTypeList",personnelTypeList)
                    .getSingleResult();
        }else {
            content = entityManager.createNativeQuery(sql, LocationPersonnelDto.class)
                    .setParameter("fnumber", locationEntity.getFloorNumber())
                    .setParameter("pg", locationEntity.getLocationPolygon())
                    .setFirstResult((int) pageable.getOffset())
                    .setMaxResults(pageable.getPageSize())
                    .getResultList();
            String countSql = "select count(*) as total from (" + sql + ") as t";
            total = (BigInteger) entityManager.createNativeQuery(countSql)
                    .setParameter("fnumber", locationEntity.getFloorNumber())
                    .setParameter("pg", locationEntity.getLocationPolygon())
                    .getSingleResult();
        }
        return new PageImpl<LocationPersonnelDto>(content, pageable, total.longValue());
    }

    @Override
    public LocationBindPersonnelDto getBindPersonnelsInfoInLocation(Long locationId) {
        PoiLocationEntity locationEntity = poiLocationRepository.getById(locationId);

        String sql = "select p.id as personnel_id, p.personnel_name, p.card_number, p.personnel_sn, p.personnel_type, p.personnel_type_name, p.org_id, p.org_name, p.locator_address, " +
                " t.id as location_id, t.location_name, t.location_type, t.map_level, t.floor_number, t.bind_org_id, " +
                " pp.is_death_prisoner, pp.is_focused_prisoner, pp.is_infected_prisoner, pp.is_major_criminal_offender, p.online_status  " +
                " from v_personnel p " +
                " left join (select * from poi_location where map_level = 3) t on st_contains(t.location_polygon, p.pos) and t.floor_number = p.floor_number" +
                " left join personnel_prisoner pp on p.id = pp.personnel_id " +
                " where p.is_enabled = 1 and p.org_id = :bindOrg ";

        List<LocationPersonnelDto> content = entityManager.createNativeQuery(sql, LocationPersonnelDto.class)
                .setParameter("bindOrg", locationEntity.getBindOrgId())
                .getResultList();
        if (content != null && content.size() > 0) {
            int count = 0;
            for (LocationPersonnelDto dto : content) {
                if (dto.getOrgId().equals(dto.getBindOrgId())) {
                    count++;
                }
            }
            return new LocationBindPersonnelDto(content.size(), count, content);
        }
        return new LocationBindPersonnelDto(0, 0);
    }
}
